/*
 *  Arrow
 *
 *  Created by David Giovannini on 1/5/07.
 *  Copyright 2007 Software by Jove. All rights reserved.
 *
 */
 
#if defined(WIN32)
#include "SBJ/Time/TimeInvoker.h"
#include "SBJ/Thread/PlatformDetails.h"
#include "SBJ/Interfaces/IExceptionObserver.h"

#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>

#include <vector>
#include <hash_set>

#include "windows.h"

namespace SBJ
{

class TimeInvoker::Pimpl
{
public:
	Pimpl( void );

	~Pimpl( void );

	void start( void );

	void stop( bool join );

	void addEntry( Entry* entry );

	void cancelEntry( Entry* entry );

	void join( void );

private:
	IExceptionObserver::Ptr m_excObserver;
	boost::scoped_ptr< boost::thread > m_messageThread;
	boost::mutex m_guard;
	boost::condition m_readyCond;
	bool m_started;

	HWND m_hwnd;

	void threadWork( void );
};

const UINT WM_ADD_ENTRY = WM_USER + 1;
const UINT WM_CANCEL_ENTRY = WM_USER + 3;
const UINT WM_STOP_TIMER = WM_USER + 4;

TimeInvoker::Pimpl::Pimpl( void )
: m_started( false )
, m_hwnd( 0 )
{
}

TimeInvoker::Pimpl::~Pimpl( void )
{
	stop( true );
}

struct hasIt
{
	hasIt( HWND& it ) : m_it( it ) {}
	bool operator () ( void ) const { return ( m_it != 0 ); }
	const HWND& m_it;
};

void
TimeInvoker::Pimpl::start()
{
	if ( m_started == false )
	{
		// Create the execution thread
		m_messageThread.reset(
			new boost::thread(
				boost::bind( &Pimpl::threadWork, boost::ref( *this ) ) ) );
		{
			boost::mutex::scoped_lock lock( m_guard );
			m_readyCond.wait( lock, hasIt( m_hwnd ) );
			m_started = true;
		}
	}
}

void
TimeInvoker::Pimpl::join( void )
{
	if ( m_started == true )
	{
		m_messageThread->join();
	}
}

void
TimeInvoker::Pimpl::stop( bool join )
{
	if ( m_started == true )
	{
		m_started = false;
		::PostMessage( m_hwnd, WM_STOP_TIMER, 0, 0 );
		if ( join == true )
		{
			m_messageThread->join();
		}
	}
}

void
TimeInvoker::Pimpl::addEntry( Entry* entry )
{
	if ( m_started == true )
	{
		::PostMessage( m_hwnd, WM_ADD_ENTRY, reinterpret_cast< WPARAM >( entry ), 0 );
	}
	else
	{
		delete entry;
	}
}

void
TimeInvoker::Pimpl::cancelEntry( Entry* entry )
{
	if ( m_started == true )
	{
		::PostMessage( m_hwnd, WM_CANCEL_ENTRY, reinterpret_cast< WPARAM >( entry ), 0 );
	}
}

void
TimeInvoker::Pimpl::threadWork( void )
{
	PlatformDetails::ThreadGuard threadGuard;
	// m_started is not thread-safe - do not use

	// In order to have user definable data in the call to SetTimer
	// we have to have an hwnd and the hwnd has to be created in this thread
	{
		boost::mutex::scoped_lock lock( m_guard );
		m_hwnd = ::CreateWindow( "STATIC", "TimerService", 0, 0 , 0, 0, 0, NULL, NULL, NULL, NULL );
		m_readyCond.notify_one();
	}

	typedef stdext::hash_set< Entry* > EntrySet;
	EntrySet owned;

	// Maintain the timers on our Windows Message pump
	while ( true )
	{
		MSG msg;
		BOOL bRet = ::GetMessage( &msg, m_hwnd, WM_TIMER, WM_STOP_TIMER );
		if ( bRet != 0 && bRet != -1 )
		{
			Entry* entry = reinterpret_cast< Entry* >( msg.wParam );
// If added
			if ( msg.message == WM_ADD_ENTRY )
			{
				owned.insert( entry );
				::SetTimer( m_hwnd, reinterpret_cast< UINT_PTR >( entry ), 
					static_cast< UINT >( entry->getInterval() * 1000.0 ), 0 );
			}
// If canceled
			else if ( msg.message == WM_CANCEL_ENTRY )
			{
				EntrySet::iterator findIt( owned.find( entry ) );
				if ( findIt != owned.end() )
				{
					Entry* toKill = *findIt;
					owned.erase( findIt );
					::KillTimer( m_hwnd, reinterpret_cast< UINT_PTR >( toKill ) );
					try
					{
						delete toKill;
					}
					catch ( std::exception& exception )
					{
						if ( m_excObserver ) m_excObserver->onException( exception );
					}
					catch ( ... )
					{
						if ( m_excObserver ) m_excObserver->onException( std::logic_error( "Unknwon Timer delete error" ) );
					}
				}
			}
// If exiting
			else if ( msg.message == WM_STOP_TIMER )
			{
				break;
			}
// If executed
			else if ( msg.message == WM_TIMER )
			{
				double oldInterval = entry->getInterval();
				bool doAgain = false;
				try
				{
					doAgain = entry->execute();
				}
				catch ( std::exception& exception )
				{
					if ( m_excObserver ) m_excObserver->onException( exception );
				}
				catch ( ... )
				{
					if ( m_excObserver ) m_excObserver->onException( std::logic_error( "Unknwon Timer execution error" ) );
				}
				// If the timer says its task is done
				if ( doAgain == false )
				{
					// Lose knowledge of it
					EntrySet::iterator findIt( owned.find( entry ) );
					if ( findIt != owned.end() )
					{
						Entry* toKill = *findIt;
						owned.erase( findIt );
						::KillTimer( m_hwnd, reinterpret_cast< UINT_PTR >( toKill ) );
						try
						{
							delete toKill;
						}
						catch ( std::exception& exception )
						{
							if ( m_excObserver ) m_excObserver->onException( exception );
						}
						catch ( ... )
						{
							if ( m_excObserver ) m_excObserver->onException( std::logic_error( "Unknwon Timer delete error" ) );
						}
					}
				}
				// If we continue and interval has changed, re-issue
				else if ( oldInterval != entry->getInterval() ) // if change in interval
				{
					::KillTimer( m_hwnd, reinterpret_cast< UINT_PTR >( entry ) );
					::SetTimer( m_hwnd, reinterpret_cast< UINT_PTR >( entry ), 
						static_cast< UINT >( entry->getInterval() * 1000.0 ), 0 );
				}
			}
		}
    }

	// Kill any activated timers
	if ( owned.empty() == false )
	{
		EntrySet::iterator iter = owned.begin();
		EntrySet::iterator end = owned.end();
		while ( iter != end )
		{
			Entry* toKill = *iter;
			// don't erase
			::KillTimer( m_hwnd, reinterpret_cast< UINT_PTR >( toKill ) );
			try
			{
				delete toKill;
			}
			catch ( std::exception& exception )
			{
				if ( m_excObserver ) m_excObserver->onException( exception );
			}
			catch ( ... )
			{
				if ( m_excObserver ) m_excObserver->onException( std::logic_error( "Unknwon Timer delete error" ) );
			}
			++iter;
		}
		owned.clear();
	}
	::DestroyWindow( m_hwnd );
	m_hwnd = 0;
}

TimeInvoker::TimeInvoker( void )
: m_pimpl( new Pimpl )
{
}

TimeInvoker::~TimeInvoker( void )
{
}

void
TimeInvoker::start( void )
{
	m_pimpl->start();
}

void
TimeInvoker::join( void )
{
	m_pimpl->join();
}

void
TimeInvoker::stop( bool join )
{
	m_pimpl->stop( join );
}

void
TimeInvoker::addEntry( Entry* entry )
{
	m_pimpl->addEntry( entry );
}

void
TimeInvoker::cancelEntry( Entry* entry )
{
	m_pimpl->cancelEntry( entry );
}

}
#endif
